/*
	Copyright (C) 2008 Demiurg-HG
	
	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.
	
	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
*/
// win_net.cpp - 

#pragma error do not include this file to project

#include "..\global.h"	
#include "win_net.h"

/*-----------------------------------------------------------------------------
	Export stuff :
-----------------------------------------------------------------------------*/
INetworkSystem *CreateNetworkSystem(IConsole *con, ISystem *sys)
{
	return new EWinNetworkSystem(con, sys);
}


void DestroyNetworkSystem(INetworkSystem *ns)
{
	delete ns;
}

/*-----------------------------------------------------------------------------
	EWinNetworkSystem
-----------------------------------------------------------------------------*/
//
//	EWinNetworkSystem::EWinNetworkSystem
//
EWinNetworkSystem::EWinNetworkSystem(IConsole *con, ISystem *sys)
{
	this->con	=	con;
	this->sys	=	sys;
	
	LOG_SPLIT("Winsock Network System initialization");
		
		int ret = WSAStartup(MAKEWORD(2,2), &wsa_data);
		if (ret) {
			RAISE_EXCEPTION(va("%s", NET_ErrorString()));
		}
		
	LOG_SPLIT("");
}


//
//	EWinNetworkSystem::~EWinNetworkSystem
//
EWinNetworkSystem::~EWinNetworkSystem(void)
{
	LOG_SPLIT("Winsock Network System shutting down");
	
		WSAStartup(MAKEWORD(2,2), &wsa_data);
	
	LOG_SPLIT("");
}



INetServer *EWinNetworkSystem::CreateServer(void)
{
	return new EWinTCPServer(this);
}

void EWinNetworkSystem::DestroyServer(INetServer *net_sv)
{
	delete net_sv;
}


INetClient *EWinNetworkSystem::CreateClient(void)
{
	return new EWinTCPClient(this);
}

void EWinNetworkSystem::DestroyClient(INetClient *net_cl)
{
	delete net_cl;
}

/*-----------------------------------------------------------------------------
	TCP Server
-----------------------------------------------------------------------------*/

//
//	EWinTCPServer::EWinTCPServer
//
EWinTCPServer::EWinTCPServer(EWinNetworkSystem *ns)
{
	con	=	ns->con;

	s_listen	=	INVALID_SOCKET;
	
	for (uint i=0; i<SV_MAXCLIENTS; i++) {
		clients[i]  = INVALID_SOCKET;
	}
}


//
//	EWinTCPServer::~EWinTCPServer
//
EWinTCPServer::~EWinTCPServer(void)
{
	if (IsRunning()) {
		Kill();
	}
}


//
//	EWinTCPServer::IsRunning
//
bool EWinTCPServer::IsRunning(void)
{
	if (s_listen==INVALID_SOCKET) {
		return false;
	} else {
		return true;
	}
}


//
//	EWinTCPServer::Run
//
void EWinTCPServer::Run(word port)
{
	if (IsRunning()) {
		RAISE_EXCEPTION("Server is already running");
	}

	SOCKADDR_IN		local;
	unsigned long	ul	= 1;
	int				ret = 0;

	LOGF("Starting server : port %d\r\n", port);
	
	for (uint i=0; i<SV_MAXCLIENTS; i++) {
		clients[i]  = INVALID_SOCKET;
	}

	// create socket	
	LOG_ACTION("Socket creation");
	s_listen	=	socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
	if (s_listen==INVALID_SOCKET) {
		LOG_FAILED();
		RAISE_EXCEPTION(va("socket() failed : %s", NET_ErrorString()));
	}
	LOG_OK();

	// bind socket
	LOG_ACTION("Socket binding");
	local.sin_family		= AF_INET;
	local.sin_port			= htons(port);
	local.sin_addr.s_addr	= htonl(INADDR_ANY);

	ret = bind(s_listen, (SOCKADDR*)&local, sizeof(local));
	if ( ret==SOCKET_ERROR ) {
		LOG_FAILED();
		RAISE_EXCEPTION(va("bind() failed : %s", NET_ErrorString()));
	}
	LOG_OK();

	// switch socket to listening mode...
	LOG_ACTION("Switching socket to listening mode");
	ret	= listen(s_listen, 0);
	if ( ret==SOCKET_ERROR ) {
		LOG_FAILED();
		RAISE_EXCEPTION(va("listen() failed : %s", NET_ErrorString()));
	}
	LOG_OK();

	// switch socket in unblocked mode...
	LOG_ACTION("Switching socket to unblocking mode");
	ret = ioctlsocket(s_listen, FIONBIO, &ul);
	if (ret==SOCKET_ERROR) {
		LOG_FAILED();
		RAISE_EXCEPTION(va("ioctlsocket() failed : %s", NET_ErrorString()));
	}
	LOG_OK();
}


//
//	EWinTCPServer::Kill
//
void EWinTCPServer::Kill(void)
{
	if (!IsRunning()) {	
		RAISE_EXCEPTION("Server is not running");	
	}
	
	//	DHG : check valid shutting down sequence :
	for (uint i=0; i<SV_MAXCLIENTS; i++) 
	{
		if (clients[i]!=INVALID_SOCKET) 
		{
			closesocket( clients[i] );
		}
	}

	closesocket(s_listen);
	s_listen	=	INVALID_SOCKET;

	LOGF("Server shutted down\r\n");
}


//
//	EWinTCPServer::AcceptClient
//
void EWinTCPServer::AcceptClient(void)
{
	if (!IsRunning()) {	
		RAISE_EXCEPTION("Server is not running");	
	}

	uint i;
	int	addr_size = sizeof(SOCKADDR_IN);
	SOCKADDR_IN cl_addr;
	SOCKET		s_refused;

	// find empty client slot :	
	for (i=0; i<SV_MAXCLIENTS; i++)	{
		if (clients[i] == INVALID_SOCKET) break;
	}

	// no client slots found :
	if (i==SV_MAXCLIENTS)
	{
		s_refused = accept(s_listen, (SOCKADDR*)&cl_addr, &addr_size);
		if (s_refused==SOCKET_ERROR)
		{
			if (WSAGetLastError()==WSAEWOULDBLOCK) return;
			
			RAISE_EXCEPTION(va("accept() failed (refusing) : %s", NET_ErrorString()));
			
		} else 
		{
			LOGF("SERVER : Client from %s:%d has been refused\r\n", inet_ntoa(cl_addr.sin_addr), cl_addr.sin_port);
			closesocket(s_refused);
		}
		return;
	}

	// trying to accept client :
	clients[i] = accept(s_listen, (SOCKADDR*)&cl_addr, &addr_size);

	if (clients[i]==INVALID_SOCKET)
	{
		if (WSAGetLastError()==WSAEWOULDBLOCK) return;
		
		RAISE_EXCEPTION(va("accept() failed : %s", NET_ErrorString()));
	} else 
	{
		LOGF("SERVER : Client #%d connected %s:%d\r\n", i, inet_ntoa(cl_addr.sin_addr), cl_addr.sin_port);
	}
}


//
//	EWinTCPServer::DisconnectClient
//
void EWinTCPServer::DisconnectClient(uint cl, int err_code)
{
	if (!IsRunning()) {	
		RAISE_EXCEPTION("Server is not running");	
	}

	int ret;
	cl %= SV_MAXCLIENTS;

	ret = closesocket(clients[cl]);
	clients[cl] = INVALID_SOCKET;

	if (err_code) {
		LOGF("SERVER : Client #%d has been disconnected with error %s\r\n", cl, NET_ErrorString());
	} else {
		LOGF("SERVER : Client #%d has been disconnected\r\n", cl);
	}
}


void EWinTCPServer::SwitchClient(uint cl)
{
	current_client	=	cl;
}


//
//	EWinTCPServer::Send
//
int EWinTCPServer::Send(void *data, uint size)
{
	uint cl = current_client;
	if (!IsRunning()) {	
		RAISE_EXCEPTION("Server is not running");	
	}

	int ret;
	cl %= SV_MAXCLIENTS;
	
	if (clients[cl]==INVALID_SOCKET) {
		RAISE_EXCEPTION(va("Wrong client ID %d", cl));
	}

	ret = send(clients[cl], (char*)data, size, 0);

	// socket error occured...
	if (ret==SOCKET_ERROR)
	{
		if (WSAGetLastError()==WSAEWOULDBLOCK) return 0;
		DisconnectClient(cl, WSAGetLastError());
		return 0;
	}
	
	return ret;
}


//
//	EWinTCPServer::Recv
//
int EWinTCPServer::Recv(void *data, uint size)
{
	uint cl = current_client;
	if (!IsRunning()) {	
		RAISE_EXCEPTION("Server is not running");	
	}

	int ret;
	cl %= SV_MAXCLIENTS;
	
	if (clients[cl]==INVALID_SOCKET) {
		RAISE_EXCEPTION(va("Wrong client ID %d", cl));
	}

	ret = recv(clients[cl], (char*)data, size, 0);

	// socket error occured...
	if (ret==SOCKET_ERROR)
	{
		if (WSAGetLastError()==WSAEWOULDBLOCK) return 0;
		DisconnectClient(cl, WSAGetLastError());
		return 0;
	}
	
	if (ret==0)
	{
		DisconnectClient(cl, 0);
		return 0;
	}

	return ret;
}


//
//	EWinTCPServer::IsConnected
//
bool EWinTCPServer::IsClientConnected(uint cl)
{
	if (!IsRunning()) {	
		RAISE_EXCEPTION("Server is not running");
		//	DHG : should I return "false"?
	}

	cl %= SV_MAXCLIENTS;
	
	if (clients[cl]==INVALID_SOCKET) return false;
	
	return true;
}


/*-----------------------------------------------------------------------------
	TCP Client
-----------------------------------------------------------------------------*/

//
//	EWinTCPClient::EWinTCPClient
//
EWinTCPClient::EWinTCPClient(EWinNetworkSystem *ns)
{
	con			=	ns->con;
	s_client	=	INVALID_SOCKET;
}


//
//	EWinTCPClient::~EWinTCPClient
//
EWinTCPClient::~EWinTCPClient(void)
{
	if (IsConnected()) {
		Disconnect();
	}
}


//
//	EWinTCPClient::Connect
//
void EWinTCPClient::Connect(const char *addr, word port)
{
	if (IsConnected()) {
		RAISE_EXCEPTION("Client already connected");
	}

	ELock lock(con);
	LOG_SPLIT("Connection establishing");

	unsigned long	ul = 1;
	int ret;
	int	addr_size = sizeof(SOCKADDR_IN);
	struct in_addr	ip;
	SOCKADDR_IN		sv_addr;
	HOSTENT			*host;
	
	port &= 0xFFFF;
	LOGF("Connecting to server : %s:%d\r\n", addr, port);

	LOG_ACTION("Socket creation");	
	s_client = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (s_client==INVALID_SOCKET)
	{
		LOG_FAILED();
		RAISE_EXCEPTION(va("socket() failed : %s", NET_ErrorString()));
	}
	LOG_OK();

	// resolving server name...
	ip.s_addr = inet_addr(addr);
	if (ip.s_addr==INADDR_NONE)
	{
		host = gethostbyname(addr);
		if (!host) {
			closesocket(s_client);
			s_client	=	INVALID_SOCKET;
			RAISE_EXCEPTION(va("Unable to resolve server name : %s", addr));
		}
		
		memcpy(&ip, host->h_addr_list[0], 4);
		LOGF("Name resolved : %s\r\n", inet_ntoa(ip));
	}

	sv_addr.sin_family	= AF_INET;
	sv_addr.sin_port	= htons(port);
	sv_addr.sin_addr	= ip;

	// attempt to connect...
	LOG_ACTION(va("Connecting to %s:%d", inet_ntoa(sv_addr.sin_addr), port));
	ret = connect(s_client, (SOCKADDR*)&sv_addr, addr_size);
	if (ret==SOCKET_ERROR)
	{
		closesocket(s_client);
		s_client	=	INVALID_SOCKET;
		LOG_FAILED();
		RAISE_EXCEPTION(va("connect() failed : %s", NET_ErrorString()));
	}
	LOG_OK();

	// switch socket to unblocked mode...
	LOG_ACTION("Switching socket to unblocking mode");
	ret = ioctlsocket(s_client, FIONBIO, &ul);
	if (ret==SOCKET_ERROR) 
	{
		closesocket(s_client);
		s_client	=	INVALID_SOCKET;
		LOG_FAILED();
		RAISE_EXCEPTION(va("ioctlsocket() failed : %s", NET_ErrorString()));
	}
	LOG_OK();

	LOGF("Connection succeded\r\n");
	
	LOG_SPLIT("");
}


//
//	EWinTCPClient::Disconnect
//
void EWinTCPClient::Disconnect(void)
{
	if (!IsConnected()) {
		RAISE_EXCEPTION("Client is not connected");
	}

	ELock lock(con);

	closesocket(s_client);
	s_client = INVALID_SOCKET;
	
	LOGF("CLIENT : Disconnected from server\r\n");
}


//
//	EWinTCPClient::IsConnected
//
bool EWinTCPClient::IsConnected(void)
{
	if (s_client==INVALID_SOCKET) {
		return false;
	} else {
		return true;
	}
}


//
//	EWinTCPClient::Recv
//
int EWinTCPClient::Recv(void *data, uint size)
{
	if (!IsConnected()) {
		RAISE_EXCEPTION("Client is not connected");
	}

	int ret;

	ret = recv(s_client, (char*)data, size, 0);
	if (ret==SOCKET_ERROR)
	{	
		if (WSAGetLastError()==WSAEWOULDBLOCK) return 0;
		
		RAISE_EXCEPTION(va("recv() failed, %s", NET_ErrorString()));
	}

	if (ret==0)	{
		RAISE_EXCEPTION("Connection has been interrupted");
	}

	return ret;
}


//
//	EWinTCPClient::Recv
//
int EWinTCPClient::Send(void *data, uint size)
{
	if (!IsConnected()) {
		RAISE_EXCEPTION("Client is not connected");
	}

	int ret;
	
	ret = send(s_client, (char*)data, size, 0);
	if (ret==SOCKET_ERROR)
	{	
		if (WSAGetLastError()==WSAEWOULDBLOCK) return 0;
		
		RAISE_EXCEPTION(va("send() failed, %s", NET_ErrorString()));
	}

	return ret;
}


/*-----------------------------------------------------------------------------
	Error stuff :
-----------------------------------------------------------------------------*/
//
//	EWinNetworkSystem::NetworkErrorString
//
const char *NET_ErrorString(void)
{
	int code	=	WSAGetLastError();
	switch (code)
	{
		case WSAEINTR: return "WSAEINTR";
		case WSAEBADF: return "WSAEBADF";
		case WSAEACCES: return "WSAEACCES";
		case WSAEDISCON: return "WSAEDISCON";
		case WSAEFAULT: return "WSAEFAULT";
		case WSAEINVAL: return "WSAEINVAL";
		case WSAEMFILE: return "WSAEMFILE";
		case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK";
		case WSAEINPROGRESS: return "WSAEINPROGRESS";
		case WSAEALREADY: return "WSAEALREADY";
		case WSAENOTSOCK: return "WSAENOTSOCK";
		case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ";
		case WSAEMSGSIZE: return "WSAEMSGSIZE";
		case WSAEPROTOTYPE: return "WSAEPROTOTYPE";
		case WSAENOPROTOOPT: return "WSAENOPROTOOPT";
		case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT";
		case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT";
		case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP";
		case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT";
		case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT";
		case WSAEADDRINUSE: return "WSAEADDRINUSE";
		case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL";
		case WSAENETDOWN: return "WSAENETDOWN";
		case WSAENETUNREACH: return "WSAENETUNREACH";
		case WSAENETRESET: return "WSAENETRESET";
		case WSAECONNABORTED: return "WSAECONNABORTED";
		case WSAECONNRESET: return "WSAECONNRESET";
		case WSAENOBUFS: return "WSAENOBUFS";
		case WSAEISCONN: return "WSAEISCONN";
		case WSAENOTCONN: return "WSAENOTCONN";
		case WSAESHUTDOWN: return "WSAESHUTDOWN";
		case WSAETOOMANYREFS: return "WSAETOOMANYREFS";
		case WSAETIMEDOUT: return "WSAETIMEDOUT";
		case WSAECONNREFUSED: return "WSAECONNREFUSED";
		case WSAELOOP: return "WSAELOOP";
		case WSAENAMETOOLONG: return "WSAENAMETOOLONG";
		case WSAEHOSTDOWN: return "WSAEHOSTDOWN";
		case WSASYSNOTREADY: return "WSASYSNOTREADY";
		case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED";
		case WSANOTINITIALISED: return "WSANOTINITIALISED";
		case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND";
		case WSATRY_AGAIN: return "WSATRY_AGAIN";
		case WSANO_RECOVERY: return "WSANO_RECOVERY";
		case WSANO_DATA: return "WSANO_DATA";
		default: return "NO ERROR";
	}
}








